/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.test_instantiator;

import picounit.Lifecycle;
import picounit.features.base.exception.ClassNotFound;
import picounit.features.class_finder.ClassFinder;
import picounit.features.class_finder.FileSystem;
import picounit.features.class_finder.FindAction;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class LifecycleInstantiatorImpl implements LifecycleInstantiator {
	private final Condition implementsLifecycle = new ImplementsCondition(Lifecycle.class);

	private final Instantiator instantiator;

	private final ClassLoader classLoader;
	private final ClassFinder classFinder;
	private final FileSystem fileSystem;

	private final Map<File, List<Class<?>>> cache = new HashMap<File, List<Class<?>>>();

	public LifecycleInstantiatorImpl(ClassLoader classLoader, Instantiator instantiator,
		ClassFinder classFinder) {

		this.instantiator = instantiator;
		this.classLoader = classLoader;
		this.classFinder = classFinder;
		this.fileSystem = new FileSystem();
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// LifecycleInstantiator
	///////////////////////////////////////////////////////////////////////////////////////////////

	public Lifecycle[] instantiateLifecyclesFor(Class<?> testClass) {		
		return instantiate(findClassesInOrAbove(fileSystem.directoryContaining(testClass),
			fileSystem.sourceRoot(testClass)));
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private List<Class<?>> findClassesInOrAbove(File inOrAbove, File sourceRoot) {
		List<Class<?>> lifecycleClassList = cache.get(inOrAbove);

		if (lifecycleClassList != null) {
			return lifecycleClassList;
		}

		lifecycleClassList = new LinkedList<Class<?>>();

		classFinder.findClasses(sourceRoot, sourceRoot,
			new AboveClassDirectoryCondition(inOrAbove, sourceRoot),
			new AddLifecycle(lifecycleClassList, classLoader, implementsLifecycle));

		cache.put(inOrAbove, lifecycleClassList);

		return lifecycleClassList;
	}

	@SuppressWarnings("unchecked")
	private Lifecycle[] instantiate(List<Class<?>> lifecycleClassList) {
		List<Lifecycle> lifecycles = new ArrayList<Lifecycle>(lifecycleClassList.size());

		for (Class<?> lifecycleClass : lifecycleClassList) {
			lifecycles.add((Lifecycle) instantiator.instantiate(lifecycleClass));
		}

		return lifecycles.toArray(new Lifecycle[lifecycles.size()]);
	}

	public static class AddLifecycle implements FindAction {
		private final List<Class<?>> lifecycleList;
		private final ClassLoader classLoader;
		private final Condition isLifecycle;

		public AddLifecycle(List<Class<?>> lifecycleList, ClassLoader classLoader, Condition isLifecycle) {
			this.lifecycleList = lifecycleList;
			this.classLoader = classLoader;
			this.isLifecycle = isLifecycle;
		}

		public void perform(String className) {
			try {
				Class<?> aClass = classLoader.loadClass(className);

				if (isLifecycle.matches(aClass)) {
					lifecycleList.add(aClass);
				}
			}
			catch (ClassNotFoundException classNotFoundException) {
				throw new ClassNotFound(classNotFoundException);
			}
		}
	}
}
